package org.mozhu.mboot.backend.security;

import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.AllArgsConstructor;
import lombok.Data;
import org.mozhu.mboot.backend.AjaxResponse;
import org.mozhu.mboot.backend.HttpServlets;
import org.springframework.http.HttpStatus;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.authentication.AuthenticationFailureHandler;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

public class JWTLoginFilter extends UsernamePasswordAuthenticationFilter {


    public JWTLoginFilter(JWTService jwtService, AuthenticationManager authenticationManager) {
        this.setAuthenticationSuccessHandler(new JWTAuthenticationSuccessHandler(jwtService));
        this.setAuthenticationFailureHandler(new JWTAuthenticationFailureHandler());
        this.setAuthenticationManager(authenticationManager);
    }

    @Override
    public Authentication attemptAuthentication(HttpServletRequest req, HttpServletResponse res) throws AuthenticationException {
        LoginUser user = extractLoginUser(req);
        return getAuthenticationManager().authenticate(
                new UsernamePasswordAuthenticationToken(
                        user.getUserName(),
                        user.getPassword())
        );
    }

    private LoginUser extractLoginUser(HttpServletRequest req) throws AuthenticationException {
        try {
            return new ObjectMapper().readValue(req.getInputStream(), LoginUser.class);
        } catch (Exception e) {
            throw new BadCredentialsException("Bad Credentials", e);
        }
    }

    public static class JWTAuthenticationSuccessHandler implements AuthenticationSuccessHandler {
        private JWTService jwtService;

        public JWTAuthenticationSuccessHandler(JWTService jwtService) {
            this.jwtService = jwtService;
        }

        @Override
        public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
            String token = jwtService.generateToken(authentication);
            HttpServlets.renderResponse(response, AjaxResponse.successWithData(new LoginResult(token)));
        }
    }

    public static class JWTAuthenticationFailureHandler implements AuthenticationFailureHandler {

        @Override
        public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException, ServletException {
            response.sendError(HttpStatus.UNAUTHORIZED.value(), exception.getMessage());
        }
    }

    @Data
    public static class LoginUser {
        private String userName;
        private String password;
    }

    @Data
    @AllArgsConstructor
    public static class LoginResult {
        private String token;
    }

}